<?php


// 自动引入
function autoloadFunc($class_name){
    $class_path = ROOT.'/';
    $class_path.=$class_name.'.php';
    $class_path = str_replace('\\', '/', $class_path);
    $class_path = str_replace('//', '/', $class_path);
//    var_dump($class_path);exit;
    if (is_file($class_path)) {
        return include_once($class_path);
    } else {
    }
    return false;
}
// 自动引入类
spl_autoload_register('autoloadFunc');
class model{
    public $page;
//    public $pageHtml;//直接生成的html代码
    public $table_name;
    protected $config;//配置参数
    private $sql = array();
    private $join = array();//联表记录
    private $second = 0;// 查询缓存， 缓存单位秒  0 不缓存
    private $alias = '';//表别名
    static protected $instance;//创建的数据库实例

    public function __construct($table_name = null){
        global $dbhost,$dbname,$dbpass,$dbport,$dbuser;
        $prefix = 'xy_';
        if($table_name){
            $this->table_name = $prefix.$table_name;
        }
        $this->config = [
            'hostname'=>$dbhost,
            'database'=>$dbname,
            'username'=>$dbuser,
            'password'=>$dbpass,
            'hostport'=>$dbport,
            'charset'=>'utf8mb4',
            'prefix'=>$prefix,
        ];
    }

    //获取模型表名
    private static function getModel($modelName=''){
        if(empty($modelName)){
            $modelName = get_called_class();
            if(is_null($modelName) || !class_exists($modelName)){
                throw new Exception("模型不存在");
            }
        }
        return new $modelName;
    }

    public static function getInstance($table_name=''){
        if(null == self::$instance){
            if(!$table_name || $table_name===1){
                $model = self::getModel();
                $table_name = $model->table_name;
            }
            self::$instance = new self($table_name);
        }
        if($table_name===1){
            $model = self::getModel();
            return self::$instance->name($model->table_name);
        }else if($table_name){
            return self::$instance->name($table_name);
        }else{
            return self::$instance;
        }
    }

    public function getDb($db_config_key = 'master'){
        return $this->dbInstance($this->config, $db_config_key);
    }


    public function name($name){
        if($this->config['prefix']){
            $name = $this->config['prefix'].$name;
        }
        $this->table_name = $name;
        return $this;
    }

    //添加别名
    public function alias($name){
        $this->alias = ' AS '.$name;
        return $this;
    }
    //添加联表操作
    public function addJoin($table,$cond,$join="LEFT"){
        $this->join[] = " {$join} JOIN {$table} ON {$cond} ";
        return $this;
    }
    public function findAll($conditions = array(), $fields = '*',$sort = null, $limit = null){

        if(empty($fields))$fields='*';
        $sort = !empty($sort) ? ' ORDER BY '.$sort : '';
        $conditions = $this->_where($conditions);
        $join = '';
        if($this->join){
            foreach ($this->join as $v){
                $join.= " {$v} ";
            }
        }

        $sql = ' FROM '.$this->table_name.$this->alias.$join.$conditions["_where"];
        if($this->second>0){
            $key = md5($sql.json_encode($conditions['_bindParams']));
            if($data = spAccess('r',$key)){
                return $data;
            }
        }
        if(is_array($limit)){
            $total = $this->query('SELECT COUNT(*) as M_COUNTER '.$sql, $conditions["_bindParams"]);
            if(!isset($total[0]['M_COUNTER']) || $total[0]['M_COUNTER'] == 0)return array();

            $limit = $limit + array(1, 10, 10);
            $limit = $this->pager($limit[0], $limit[1], $limit[2], $total[0]['M_COUNTER']);
            $limit = empty($limit) ? '' : ' LIMIT '.$limit['offset'].','.$limit['limit'];
        }else{
            $limit = !empty($limit) ? ' LIMIT '.$limit : '';
        }
        $data = $this->query('SELECT '. $fields . $sql . $sort . $limit, $conditions["_bindParams"]);
        if($this->second>0){
            spAccess('w',$key,$data,$this->second);
            $this->second = 0;// 清除缓存标记
        }
        return $data;
    }

    public function find($conditions = array(), $fields = '*', $sort = null){
        if(empty($fields))$fields='*';
        $res = $this->findAll($conditions, $fields,$sort, 1);
        return !empty($res) ? array_pop($res) : false;
    }

    public function findValue($conditions = array(), $field, $sort = null){
        $res = $this->find($conditions , $field , $sort );
        return $res[$field]?:'';
    }

    public function update($conditions, $row){
        $values = array();
        foreach ($row as $k=>$v){
            $values[":M_UPDATE_".$k] = $v;
            $setstr[] = "`{$k}` = ".":M_UPDATE_".$k;
        }
        $conditions = $this->_where( $conditions );
        return $this->execute("UPDATE ".$this->table_name." SET ".implode(', ', $setstr).$conditions["_where"], $conditions["_bindParams"] + $values);
    }

    public function incr($conditions, $field, $optval = 1){
        $conditions = $this->_where( $conditions );
        return $this->execute("UPDATE ".$this->table_name." SET `{$field}` = `{$field}` + :M_INCR_VAL ".$conditions["_where"], $conditions["_bindParams"] + array(":M_INCR_VAL" => $optval));
    }
    public function decr($conditions, $field, $optval = 1){return $this->incr($conditions, $field, - $optval);}

    public function delete($conditions){
        $conditions = $this->_where( $conditions );
        return $this->execute("DELETE FROM ".$this->table_name.$conditions["_where"], $conditions["_bindParams"]);
    }

    public function create($row){
        $values = array();
        foreach($row as $k=>$v){
            $keys[] = "`{$k}`"; $values[":".$k] = $v; $marks[] = ":".$k;
        }
        $this->execute("INSERT INTO ".$this->table_name." (".implode(', ', $keys).") VALUES (".implode(', ', $marks).")", $values);
        return $this->dbInstance($this->config, 'master')->lastInsertId();
    }
    public function cache($second){
        $this->second = $second;
        return $this;
    }
    public function findCount($conditions){
        $conditions = $this->_where( $conditions );
        $count = $this->query("SELECT COUNT(*) AS M_COUNTER FROM ".$this->table_name.$conditions["_where"], $conditions["_bindParams"]);
        return isset($count[0]['M_COUNTER']) && $count[0]['M_COUNTER'] ? $count[0]['M_COUNTER'] : 0;
    }

    public function findSum($conditions,$field){
        $conditions = $this->_where( $conditions );
        $count = $this->query("SELECT SUM(".$field.") AS M_COUNTER FROM ".$this->table_name.$conditions["_where"], $conditions["_bindParams"]);
        return isset($count[0]['M_COUNTER']) && $count[0]['M_COUNTER'] ? $count[0]['M_COUNTER'] : 0;
    }

    public function dumpSql(){return $this->sql;}

    public function pager($page, $pageSize = 10, $scope = 10, $total){
        $this->page = null;
        if($total > $pageSize){
            $total_page = ceil($total / $pageSize);
            $page = min(intval(max($page, 1)), $total);
            $min=($page-1)*$pageSize+1;
            $max=$page*$pageSize;
            $max=$max>$total?$total:$max;
            $this->page = array(
                'total_count' => (int)$total,
                'page_size'   => $pageSize,
                'total_page'  => $total_page,
                'first_page'  => 1,
                'prev_page'   => ( ( 1 == $page ) ? 1 : ($page - 1) ),
                'next_page'   => ( ( $page == $total_page ) ? $total_page : ($page + 1)),
                'last_page'   => $total_page,
                'current_page'=> $page,
                'all_pages'   => array(),
                'offset'      => ($page - 1) * $pageSize,
                'limit'       => $pageSize,
                'min'		=>$min,
                'max'		=>$max,
            );

            $scope = (int)$scope;
            if($total_page <= $scope ){
                $this->page['all_pages'] = range(1, $total_page);
            }elseif( $page <= $scope/2) {
                $this->page['all_pages'] = range(1, $scope);
            }elseif( $page <= $total_page - $scope/2 ){
                $right = $page + (int)($scope/2);
                $this->page['all_pages'] = range($right-$scope+1, $right);
            }else{
                $this->page['all_pages'] = range($total_page-$scope+1, $total_page);
            }
        }
        //直接输出html
//        $this->pageHtml = ( new page($this->page['total_count'],$this->page['current_page'],$this->page['page_size']))->html;
        return $this->page;
    }

    public function query($sql, $params = array()){return $this->execute($sql, $params, true);}
    public function execute($sql, $params = array(), $readonly = false){
        $this->sql[] = $sql;

        /*        if($readonly && !empty($GLOBALS['mysql']['MYSQL_SLAVE'])){
                    $slave_key = array_rand($GLOBALS['mysql']['MYSQL_SLAVE']);
                    $sth = $this->dbInstance($GLOBALS['mysql']['MYSQL_SLAVE'][$slave_key], 'slave_'.$slave_key)->prepare($sql);
                }else{
                    $sth = $this->dbInstance($GLOBALS['mysql'], 'master')->prepare($sql);
                }
        */
//        core::logger(array(
//            'TYPE' => 'MYSQL',
//            'SQL'  => $sql,
//        ));
        $sth = $this->dbInstance($this->config, 'master')->prepare($sql);

        if(is_array($params) && !empty($params)){
            foreach($params as $k => &$v){
                if(is_int($v)){
                    $data_type = PDO::PARAM_INT;
                }elseif(is_bool($v)){
                    $data_type = PDO::PARAM_BOOL;
                }elseif(is_null($v)){
                    $data_type = PDO::PARAM_NULL;
                }else{
                    $data_type = PDO::PARAM_STR;
                }
                $sth->bindParam($k, $v, $data_type);
            }
        }

        if($sth->execute())return $readonly ? $sth->fetchAll(PDO::FETCH_ASSOC) : $sth->rowCount();
        $err = $sth->errorInfo();
        err('Database SQL: "' . $sql. '", ErrorInfo: '. $err[2], 1);
    }

    public function dbInstance($db_config, $db_config_key, $force_replace = false){
        if($force_replace || empty($this->config['mysql_instances'][$db_config_key])){
            try {
                if(!class_exists("PDO") || !in_array("mysql",PDO::getAvailableDrivers(), true)){
                    err('Database Err: PDO or PDO_MYSQL doesn\'t exist!');
                }
                $this->config['mysql_instances'][$db_config_key] = new PDO('mysql:dbname='.$db_config['database'].';host='.$db_config['hostname'].';port='.$this->config['hostport'], $db_config['username'], $db_config['password'], array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES \''.$db_config['charset'].'\''));
            }catch(PDOException $e){err('Database Err: '.$e->getMessage());}
        }
        return $this->config['mysql_instances'][$db_config_key];
    }


    //新的查询绑定方式
    public function _where($conditions,$join_type=0){
        $result = array( "_where" => " ","_bindParams" => array());
        $and = $join_type?", ":"AND "; //拼接方式

        if(is_array($conditions) && !empty($conditions)){
            $result['_where'] = [];
            foreach ($conditions as $key=>$val){
                //如果key = a.id  则需要 变更为 a.`id`
                $newKey = $key;

                $pos = strpos($newKey,'.');
                // 将 b.id   替换为 b.`id`
                if($pos!==false){
                    $newKey = str_replace(".",'',$newKey);
                    $key = substr($key,0,$pos).'.`'.substr($key,$pos+1).'`';
                }else{
                    $key = "`{$key}`";
                }

                if(is_array($val)){
                    //特殊绑定
                    $this->filterOperator($key,$newKey,$val,$result);
                }else{
                    //直接绑定
                    $result['_where'][] = " {$key} = :{$newKey} ";
                    $result['_bindParams'][$newKey] = $val;
                }
            }

            $sql = join(" AND ",$result['_where']);
            if($sql)
                $result["_where"] = " WHERE ". $sql;
        }else{
            //字符串查询 直接返回
            $result['_where'] = $conditions ? (' where '. $conditions . ' ') :'';
        }

        return $result;
    }

    private function filterOperator($key,$newKey,$val,&$result){
        //判断数组条件数量
        $operatorValCount = count($val);
        if(!in_array($operatorValCount,[2,3])){
            array_push($result['_where'],'1=0');//参数不符合
            return;
        }
        $placeholder_type = ['=','>','>=','<','<>','<=','in','not in','between','not between','like','not like'];
        if(!in_array($val[0],$placeholder_type)){
            array_push($result['_where'],'1=2');//参数不符合
            return;
        }
        if($operatorValCount==2){
            if(in_array($val[0],['in','not in'])){
                //单独处理 in not in 不支持参数绑定的问题
                $where = $val[1];
                if(!is_array($where)) $where = explode(',',$where);
                $newBind = [];
                foreach ($where as $k=>$v){
                    if(!$v)continue;
                    array_push($newBind,"'{$v}'");
                    unset($k);
                    unset($v);
                }
                if($newBind){
                    $result['_where'][] = "{$key} {$val[0]} (".join(',',$newBind).")";
                }else{
                    $result['_where'][] = "1=3";
                }
            }else{
                $result['_where'][] = "{$key} {$val[0]} :{$newKey}";
                $result['_bindParams'][$newKey] = $val[1];
            }
        }else{
            //between
            $result['_where'][] = " {$key} {$val[0]} :{$newKey}1 AND :{$newKey}2 ";
            $result['_bindParams'][$newKey."1"] = $val[1];
            $result['_bindParams'][$newKey."2"] = $val[2];
        }
    }

    public function _where2($conditions){
        $result = array( "_where" => " ","_bindParams" => array());
        if(is_array($conditions) && !empty($conditions)){
            $fieldss = array(); $sql = null; $join = array();
            if(isset($conditions[0]) && $sql = $conditions[0]) unset($conditions[0]);
            foreach( $conditions as $key => $condition ){
                $newKey = $key;
                $isArray = false;
                if(substr($key, 0, 1) != ":"){
                    unset($conditions[$key]);
                    $pos = strpos($newKey,'.');
                    // 将 b.id   替换为 b.`id`
                    if($pos!==false){
                        $newKey = str_replace(".",'',$newKey);
                        $key = substr($key,0,$pos).'.`'.substr($key,$pos+1).'`';
                    }else{
                        $key = "`{$key}`";
                    }
                    //如果是数组 并且 有俩个元素
                    if(is_array($condition) && count($condition)==2){
                        $isArray = true;
                        $conditions[":".$newKey] = $condition[1];
                    }else{
                        $conditions[":".$newKey] = $condition;
                    }
                }
                if($isArray){
                    $join[] = "{$key} {$condition[0]} :{$newKey}";
                }else{
                    $join[] = "{$key} = :{$newKey}";
                }
            }
            if(!$sql) $sql = join(" AND ",$join);
            if($sql){
                $result["_where"] = " WHERE ". $sql;
                $result["_bindParams"] = $conditions;
            }
        }else{
            $result['_where'] = $conditions ? (' where '. $conditions . ' ') :'';
        }
        return $result;
    }
}